Concurrency control is a vital part of any
relational database management system and in SQL Server this is
implemented primarily through Locking. Locking involves reserving
resources (usually data pages) for a particular purpose; this can
affect the availability of those resources. When a single task reserves
lots of resources or runs for an excessive time blocking can occur,
which affects system performance. Deadlocks are a situation when two
tasks each want to reserve a resource that the other already has locked—a vicious circle occurs where neither can complete without the other first releasing its locks.
Locking
Locking
is responsible for ensuring database users don’t interfere with each
other, and is an essential component of any Relational Database
Management system. Locking provides concurrency control within the
database engine. A transaction is one or more actions, treated as a
unit of work. There are a set of database properties that help
understand transactions and the importance of locking:
Atomic
An atomic transaction must either complete or fail. No aspect of the
transaction must be allowed to succeed or fail without all changes
succeeding or failing. Should one modification fail, the whole
transaction should be rolled back.
Consistent Data must be consistent when a transaction completes. Data integrity must not be compromised by a transaction.
Isolated
User transactions should be isolated from each other. Data affected by
one transaction can be accessed by other transactions only in the state
it was before the transaction began, or after the transaction completes.
Durable Once a transaction has completed the changes must persist, even if there is a crash or system failure.
SQL
Server uses careful transaction management to enforce Atomicity and
Consistency. The Transaction Log ensures transaction durability since
all changes are first written to the transaction log. Finally,
Isolation is provided by locking within the database engine.
Blocking
We
should consider locking to be completely normal activity within the
database engine. When there are lots of users working on a SQL Server
database or in a situation with a poor index strategy or badly written
queries there can be many thousands of locks. If a transaction holds
many locks or holds locks for a long time, eventually this can cause
blocking. Blocking occurs when a transaction is waiting for a lock on a
resource, usually because a lock is already held by another task.
Deadlocks
Excessive
blocking can significantly impact performance and as such, it’s really
important to get visibility of tasks that are blocking each other and
investigate options to help them coexist. Severe blocking can lead to
deadlocks, which will result in SQL Server ending one session, causing
a rollback. In a deadlock situation a vicious circle can occur when two
(sometimes more) tasks, each already holding locks, are waiting for
locks that the other holds. Figure 1
shows an illustration. It’s called a deadlock because both processes
could wait endlessly for the other to release the locks it needs unless
SQL Server intervened. There’s a mechanism within SQL Server to detect
and end deadlocks whereby SQL Server will end the task that is least
expensive to rollback.
Transaction Isolation Levels
SQL
Server provides five isolation levels for transactions, which include
various pessimistic to optimistic locking strategies. These isolation
levels provide control over a balance between concurrency and data
integrity. Isolation levels may be selected based
on application requirements for data consistency and user concurrency.
Isolation levels may provide good concurrency (nonlocking), but may
provide inconsistent data; this may be acceptable in some scenarios. In
other cases, data consistency is important, as such performance may be
sacrificed in favor of consistent data.
Lock Escalation
SQL
Server always tries to lock the fewest resources possible to maximize
concurrency. Fewer locks provides good concurrency but there is an
overhead in system resources while managing locks. There are times when
SQL Server is managing so many individual locks that it becomes more
efficient to increase a single lock on an object at a higher level—this
is lock escalation.
If
a task holds a large number of row or key-range locks, these may be
escalated to a table lock. If SQL Server initially takes page locks,
these can be escalated to table locks. SQL Server will never escalate
from row to page level locks. A new feature introduced in SQL Server
2008 is that with partitioned tables, lock escalation may occur to the
partition level, instead of the table. SQL Profiler includes an event
for lock escalation (Lock:Escalation).
Lock Compatibility
There
are approximately 22 lock types in SQL Server 2008, each providing a
different lock type for resources. Some lock types allow other
processes to read the data, too (also depends on transaction isolation
level), whereas other lock types require exclusive access to data and
will wait for existing tasks to complete and release locks before
taking their own locks.
There’s
a lock compatibility matrix that shows the logic around which locks can
be taken simultaneously. SQL Server Books Online includes a full lock
compatibility matrix, and Table 1 contains a compatibility summary of the six most common lock types.
Table 1. Common Lock Compatibility Matrix
| | | Existing Lock | | | |
---|
Requested Lock | IS | S | U | IX | SIX | X |
---|
Intent Shared (IS) | | | | | | |
Shared (S) | | | | | | |
Update (U) | | | | | | |
Intent Exclusive (IX) | | | | | | |
Shared with Intent Exclusive (SIX) | | | | | | |
Exclusive (X) | | | | | | |
Detecting and Resolving Locking Problems
SQL
Server 2008 Management Studio has an Activity Monitor that shows
sessions and their state (whether they’re running, waiting, or
sleeping). Activity Monitor is available by right-clicking the instance
name within Management Studio and features a new look for SQL Server
2008 (see Figure 2).
SQL
Server Profiler provides good information; in particular, there’s a
blocked process report that can capture locking details for sessions
exceeding the blocked process threshold (controlled by sp_configure).
In situations where there’s an intermittent performance problem and
blocking is a suspect, the blocked process report can be useful. There
are a number of DMVs that provide visibility of locks held by specific
sessions and any blocked processes. The DMVs sys.dm_exec_requests shows
sessions that are suspended waiting for a lock. Additionally, querying
sys.dm_tran_locks shows more detailed locking information.
If
blocking problems are serious there’s a good chance deadlocks may also
occur. SQL Server Profiler provides a good level of information,
including Deadlock Chain (all sessions involved in the deadlock).
Profiler can also capture a deadlock graph, which reveals the commands
being executed, locks held, and the session nominated as the deadlock
victim.
Sometimes
deadlocks can be few and far between, but they can still cause problems
and a continuous Profiler trace may seem excessive to identify the
problem. In this scenario Trace Flag 1222 can be useful since this will
output deadlock details to the SQL Server Error Log.
Once
deadlock information has been captured in the SQL Server Error Log,
review the processes that are conflicting. Sometimes they’re different
processes and at other times they’re multiple instances of the same
query or stored procedure. Try to rework the query or stored procedure
to overcome the locking problem, perhaps reduce the size of the
transaction, create a new index, or use an alternative join strategy.
Trace
Flags can be enabled from SQL Server Configuration Manager, using the
–T1222 startup parameter or by using DBCC traceon (1222, –1). The DBCC
traceon method will not require a service restart, however making the
change through Configuration Manager requires a service restart.